import binascii

from rtype import RelStrVar
from pathlib import Path
import subprocess
import re
import tkinter as tk
import tkinter.ttk as ttk
from rftp import RFtp
import difflib
import io
import os
import datetime
import git

def check_version_valid(ver: str):
    pattern = re.compile(r'\S\d\d\d', re.I)
    return pattern.fullmatch(ver)

class RConstDB:
    def __init__(self):
        self._env_file = 'env.h'
        self._make_h = 'make.h'
        self._customer_include_dir = 'core/code/customer/dlinkme/inc/'
        self._customer_model_head_file = 'cust_model.h'
        self._repo_model = ('ENV', 'BOOT', 'OS', 'SDK', 'ICORE', 'CUS')
        self._ftp_fw_path = '/Project/Switch/DLINK-ME/Firmware/'
        self._ftp_fwbk_path = '/Project/Switch/DLINK-ME/Firmware/backup/'
        # Special fw version, no HW type
        self._special_model_name = ['WS5-DGS-1210-12TS', 'WS5-DGS-1210-28X', 'WS5-DGS-1210-28XS']
        self._cameotag_in_hex = '43414d454f544147'  # 'CAMEOTAG'
        self._des_model_with_SI_DU = ['DES-1210-10ME', 'DES-1210-26ME', 'DES-1228-D1', 'DGS-1100-06ME'];

    def cameo_tag_in_hex(self):
        return self._cameotag_in_hex

    def make_h(self) -> str:
        return self._make_h

    def env_file(self) -> str:
        return self._env_file

    def ftp_fw_path(self, ver: str = ''):
        return self._ftp_fw_path + ver

    def ftp_fwbk_path(self, ver: str = ''):
        return self._ftp_fwbk_path + ver

    def cust_inc_dir(self) -> str:
        return self._customer_include_dir

    def cust_file(self) -> str:
        return self._customer_model_head_file

    def repo_model(self) -> ():
        return self._repo_model

    def is_special(self, model_name:str):
        return model_name in self._special_model_name

    def is_des_SI_DU(self, model_name:str):
        return model_name in self._des_model_with_SI_DU

constdb = RConstDB()


class ModelDB:
    def __init__(self, dir: Path, model: str, rdb):
        self._db = rdb
        self._dir = dir
        """ The model directory """
        self._model = model
        """ The model name """
        if constdb.is_des_SI_DU(model):
            cust_f = 'R6_SI/' + constdb.cust_file()
        else:
            cust_f = constdb.cust_file()
        self._cust_model = dir.joinpath(
            constdb.cust_inc_dir() + model + '/' + cust_f)
        """ Path to cust_model.h in this model """
        self._hw_version = {}
        self._fw_version = {}
        self._model_alias = {}
        self._mib_hw_version = ''
        self._mib_sw_version = {}
        self._gitinfo_inmake = ''
        """ Output by 'make git-info' """
        self._repos = []
        """ List: (Git Repository) """
        self._repo_br_local = {}
        self._repo_br_remote = {}
        self._repo_br = {}
        """ Dict: (Repository --> RelStrVar)
            the textvariable bind to repo br combobox
            ps: http://www.tcl.tk/man/tcl8.6/TkCmd/ttk_combobox.htm#M12 """
        self._var_ftp_fw_dir = None
        self._var_ftp_allhex_dir = None
        self._var_ftp_backup_dir = None
        self._var_last_releasenote_dir = None
        self._var_local_fw_dir = None
        self._var_local_allhex_dir = None
        self._var_local_backup_dir = None
        self._bvar_releasenote_local = None
        self._bvar_need_release = None
        self._var_new_feature = None
        self._var_bug_fixed = None
        self._text_nf = None
        self._text_bf = None
        self._checksum_runtime = ''
        self._checksum_prom = ''

    def set_tk_text_nf(self, t: tk.Text):
        self._text_nf = t

    def get_tk_text_nf(self):
        return self._text_nf

    def set_tk_text_bf(self, t: tk.Text):
        self._text_bf = t

    def get_tk_text_bf(self):
        return self._text_bf

    def sync_repo_br(self, repo: str):
        """ TODO: call shell cmds to set repo branch """
        self._repo_br[repo].sync()
        raw = self._repo_br[repo].get_raw()
        r = git.Repo(repo)
        g = r.git
        g.checkout('.')
        if 'HEAD' in raw and 'master' in raw:
            return
        if 'origin' in raw:
            g.checkout('-b', raw.split('/')[-1], raw)
        else:
            g.checkout(raw)
        g.pull()

    def sync_repo_br_all(self):
        for i in self._repo_br:
            self.sync_repo_br(i)

    def var_repo_br(self, repo: str) -> RelStrVar:
        return self._repo_br[repo].var()

    def repos(self):
        return self._repos

    def __str__(self):
        return self._model

    def ftp_model_dir_name(self):
        name = self._model_alias['hori_line_with_slash'].replace('/', '-')
        if constdb.is_special(self._model) is False:
            if 'B1' == self._hw_version['version_tail']:
                name = name + '-' + self._hw_version['version_tail']
        return name

    def auto_detect_ftp_path(self):
        ftp = RFtp(self._db.get_ftp_host())
        ftp.login(self._db.get_ftp_user(), self._db.get_ftp_passwd())
        version = self._fw_version['version_tail']
        for repo in self._repos:
            if 'core' in repo:
                ver = self._repo_br[repo].get_var().split('_')[-1]
                if check_version_valid(ver):
                    version = ver

        fw_path = constdb.ftp_fw_path(ver=version)
        fwbk_path = constdb.ftp_fwbk_path(ver=version)
        fwall_path = constdb.ftp_fw_path(ver=version) + '/AllHEX'

        model_dir_name = self.ftp_model_dir_name()
        ftp_model_normal_path = ftp.search_and_get_dir_path(fw_path, model_dir_name)
        ftp_model_backup_path = ftp.search_and_get_dir_path(fwbk_path, model_dir_name)
        allents = ftp.get_dirlist_in_path(fwall_path)
        seriname = ftp_model_normal_path.split('/')[-2]
        m = 0
        ent = ''
        for i in allents:
            rat = difflib.SequenceMatcher(None, i, seriname).ratio()
            if rat > m:
                ent = i
        self.set_ftp_allhex_dir(ent)
        if ftp_model_normal_path is not None:
            self.set_ftp_fw_dir(ftp_model_normal_path)
        if ftp_model_backup_path is not None:
            self.set_ftp_backup_dir(ftp_model_backup_path)

        allver = ftp.get_dirlist_in_path(constdb.ftp_fw_path().rstrip('/'))
        allver_t = [(i.split('/')[-1], i) for i in allver]
        last_path = None
        for e in allver_t[1:]:
            if version == e[0]:
                last_path = allver_t[allver_t.index(e) - 1][1]

        if last_path:
            l = [last_path, ] + ftp_model_normal_path.split('/')[-2:]
            fs = ftp.get_filelist_in_path('/'.join(l))
            for f in fs:
                if f.split('.')[-1] == 'txt':
                    self.set_last_releasenote_dir(f)
                    self.set_bvar_releasenote_local(False)

        ftp.close()

    def init(self):
        self._init_params()
        self._init_gitinfo_inmake()
        self._init_git_info()
        self._init_ftp_info()
        self._init_nf_bf()

    def _recurs_find_repo(self, d: Path):
        try:
            if not d.is_symlink() and d.is_dir():
                if d.parts[-1] == '.git':
                    self._repos.append(d.__str__())
                    rp = git.Repo(d.__str__())
                    g = rp.git
                    g.pull()
                    local = [i.strip('* ') for i in rp.git.branch().split('\n')]
                    remote = [i.strip('* ') for i in rp.git.branch('-r').split('\n')]
                    self._repo_br_local[d.__str__()] = local
                    self._repo_br_remote[d.__str__()] = remote
                else:
                    for e in d.iterdir():
                        self._recurs_find_repo(e)
        except:
            print("except occurred....")

    def get_all_branches(self, repo: str):
        return self._repo_br_local[repo] + self._repo_br_remote[repo]

    def sync_db(self):
        """ Model settings confirmed, sync RelStrVar StringVar to raw value """
        self._var_ftp_fw_dir.sync()
        self._var_ftp_allhex_dir.sync()
        self._var_ftp_backup_dir.sync()
        self._var_local_fw_dir.sync()
        self._var_local_allhex_dir.sync()
        self._var_local_backup_dir.sync()
        self._var_last_releasenote_dir.sync()
        self._var_new_feature.sync()
        self._var_bug_fixed.sync()

    def ver_tail(self):
        return self._fw_version['version_tail']

    def var_local_fw_dir(self):
        return self._var_local_fw_dir.var()

    def set_local_fw_dir(self, p: str):
        self._var_local_fw_dir.set_var(value=p)
        self._var_local_fw_dir.sync()

    def get_local_fw_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_local_fw_dir.get_var()
        else:
            return self._var_local_fw_dir.get_raw()

    def var_local_allhex_dir(self):
        return self._var_local_allhex_dir.var()

    def set_local_allhex_dir(self, p: str):
        self._var_local_allhex_dir.set_var(value=p)
        self._var_local_allhex_dir.sync()

    def get_local_allhex_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_local_allhex_dir.get_var()
        else:
            return self._var_local_allhex_dir.get_raw()

    def var_local_backup_dir(self):
        return self._var_local_backup_dir.var()

    def set_local_backup_dir(self, p: str):
        self._var_local_backup_dir.set_var(value=p)
        self._var_local_backup_dir.sync()

    def get_local_backup_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_local_backup_dir.get_var()
        else:
            return self._var_local_backup_dir.get_raw()

    def var_ftp_fw_dir(self):
        return self._var_ftp_fw_dir.var()

    def set_ftp_fw_dir(self, p: str):
        self._var_ftp_fw_dir.set_var(value=p)
        self._var_ftp_fw_dir.sync()

    def get_ftp_fw_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_ftp_fw_dir.get_var()
        else:
            return self._var_ftp_fw_dir.get_raw()

    def var_ftp_allhex_dir(self):
        return self._var_ftp_allhex_dir.var()

    def set_ftp_allhex_dir(self, p: str):
        self._var_ftp_allhex_dir.set_var(value=p)
        self._var_ftp_allhex_dir.sync()

    def get_ftp_allhex_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_ftp_allhex_dir.get_var()
        else:
            return self._var_ftp_allhex_dir.get_raw()

    def var_ftp_backup_dir(self):
        return self._var_ftp_backup_dir.var()

    def set_ftp_backup_dir(self, p: str):
        self._var_ftp_backup_dir.set_var(value=p)
        self._var_ftp_backup_dir.sync()

    def get_ftp_backup_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_ftp_backup_dir.get_var()
        else:
            return self._var_ftp_backup_dir.get_raw()

    def var_last_releasenote_dir(self):
        return self._var_last_releasenote_dir.var()

    def set_last_releasenote_dir(self, p: str):
        self._var_last_releasenote_dir.set_var(value=p)
        self._var_last_releasenote_dir.sync()

    def get_last_releasenote_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_last_releasenote_dir.get_var()
        else:
            return self._var_last_releasenote_dir.get_raw()

    def bvar_need_release(self):
        return self._bvar_need_release

    def need_release(self) -> bool:
        return self._bvar_need_release.get()

    def bvar_releasenote_local(self):
        return self._bvar_releasenote_local

    def set_bvar_releasenote_local(self, t_or_f: bool):
        self._bvar_releasenote_local.set(value=t_or_f)

    def is_last_releasenote_local(self):
        return self._bvar_releasenote_local.get()

    def var_new_feature(self):
        return self._var_new_feature.var()

    def get_new_feature(self, cur_var: bool = False):
        if cur_var:
            return self._var_new_feature.get_var()
        else:
            return self._var_new_feature.get_raw()

    def var_bug_fixed(self):
        return self._var_bug_fixed.var()

    def get_bug_fixed(self, cur_var: bool = False):
        if cur_var:
            return self._var_bug_fixed.get_var()
        else:
            return self._var_bug_fixed.get_raw()

    def set_new_feature(self, f: str):
        self._var_new_feature.set_var(value=f)
        self._var_new_feature.sync()

    def set_bug_fixed(self, f: str):
        self._var_bug_fixed.set_var(value=f)
        self._var_bug_fixed.sync()

    def _init_nf_bf(self):
        self._var_new_feature = RelStrVar(init_var=True)
        self._var_bug_fixed = RelStrVar(init_var=True)

    def _init_ftp_info(self):
        self._var_ftp_fw_dir = RelStrVar(init_var=True)
        self._var_ftp_allhex_dir = RelStrVar(init_var=True)
        self._var_ftp_backup_dir = RelStrVar(init_var=True)
        self._var_local_fw_dir = RelStrVar(init_var=True)
        self._var_local_allhex_dir = RelStrVar(init_var=True)
        self._var_local_backup_dir = RelStrVar(init_var=True)
        self._var_last_releasenote_dir = RelStrVar(init_var=True)
        self._bvar_releasenote_local = tk.BooleanVar(value=True)

    def _init_git_info(self):
        self._recurs_find_repo(self._dir)
        for repo in self._repos:
            rp = git.Repo(repo)
            local = [i.strip(' ') for i in rp.git.branch().split('\n')]
            for i in local:
                if '*' in i:
                    self._repo_br[repo] = RelStrVar(
                        value=i.strip('* '), init_var=True)

    def _init_gitinfo_inmake(self):
        sh = 'make -C %s git-info --no-print-directory' % str(self._dir)
        (status, output) = subprocess.getstatusoutput(sh)
        # by the way, update git_info in model_params dict
        lines = output.split('\n')
        git_info = ''
        git_info += '(model)  (repertory)          (branch)             (version)      (commit by)      (date)\r\n'
        for l in lines:
            larr = l.split()
            if larr[0] in constdb.repo_model():
                git_info += ' %-8s %-20s%-21s%-16s%-16s%-s\r\n' % (
                    larr[0], larr[1], larr[2], larr[3][5:], larr[4][5:],
                    ('<' + re.search(r'(.*)<(.*)>(.*)', l).group(2) + '>'))
        self._gitinfo_inmake = git_info

    def _init_params(self):
        self._bvar_need_release = tk.BooleanVar(value=True)
        line = None
        with open(self._cust_model, mode='r') as f:
            bits = 0x00
            try:
                line = f.readline()
            except:
                pass
            while line:
                try:
                    arr = str(line).split()
                    if len(arr) == 3:
                        value = arr[-1].strip('"')
                        if arr[1] == 'ISS_HW_VERSION':  # {'version_dot': 'Rev.B1', 'version_tail': 'B1'}
                            self._hw_version['version_dot'] = value
                            self._hw_version['version_tail'] = value.split('.')[-1]
                            bits |= 0x01
                        elif arr[1] == 'ISS_FW_VERSION':  # {'version_dot': '7.03.D013', 'version_hori_line': '7-03-D013', 'version_tail': 'D013'}
                            self._fw_version['version_dot'] = value
                            self._fw_version['version_hori_line'] = value.replace('.', '-')
                            self._fw_version['version_tail'] = value.split('.')[-1]
                            bits |= (0x01 << 1)
                        elif arr[1] == 'CAMEO_PROJECT_SHOW_MODEL_NAME':  # {'hori_line_with_slash': 'DGS-1210-28/ME', 'hori_line_no_slash': 'DGS-1210-28ME'}
                            self._model_alias['hori_line_with_slash'] = value
                            self._model_alias['hori_line_no_slash'] = value.replace('/', '')
                            bits |= (0x01 << 2)
                        elif arr[1] == 'CAMEO_MIB_HW_VERSION':
                            self._mib_hw_version = value
                            bits |= (0x01 << 3)
                        elif arr[1] == 'CAMEO_MIB_SW_VERSION':
                            self._mib_sw_version['version_hori_line'] = value
                            self._mib_sw_version['version_dot'] = value.replace('-', '.')
                            bits |= (0x01 << 4)
                        if bits & 0x1f == 0x1f:
                            break
                    line = f.readline()
                except:
                    print("readline failed. pass...")
                    pass


    def get_release_content(self):
        text = []
        today = datetime.date.today()
        text.append('________________________________________________________________________________\r\n')
        text.append('        RELEASE VERSION  : %s\r\n' %(self._fw_version['version_dot']) )
        text.append('        RELEASE DATE     : %04d/%02d/%02d\r\n' %(today.year, today.month, today.day))
        text.append('        RELEASEE BY      : %s\r\n' %(self._db.get_release_by()))
        text.append('________________________________________________________________________________\r\n\r\n')

        text.append('[CHECK-CODE]    : \r\n')
        text.append('[CHECKSUM]      : [N/A]\r\n')
        text.append('[PART-NO]       : [N/A]\r\n')
        text.append('[GIT]           :\r\n%s\r\n\r\n' %(self._gitinfo_inmake))

        text.append('[TMPL-VER]      : [N/A]\r\n')
        text.append('[ECN-NO]        : [N/A]\r\n')
        text.append('[TP-TIME]       : [N/A]\r\n')
        text.append('[FW-VER]        : %s\r\n' %(self._fw_version['version_dot']))
        text.append('[PLM-NO]        : [N/A]\r\n')
        text.append('________________________________________________________________________________\r\n\r\n')

        text.append('New Feature:\r\n')
        text.append('-------------\r\n')
        text.append('%s\r\n\r\n' %(self.get_new_feature().replace('\n', '\r\n')))

        text.append('Bug Fixed:\r\n')
        text.append('-------------\r\n')
        text.append('%s\r\n\r\n' %(self.get_bug_fixed().replace('\n', '\r\n')))

        text.append('Changed:\r\n')
        text.append('-------------\r\n')
        text.append('N/A\r\n\r\n')


        text.append('Note:\r\n')
        text.append('-------------\r\n\r\n')

        text.append('Version :\r\n')
        text.append('    HW      : %s\r\n' %(self._hw_version['version_tail']))
        text.append('    PROM    : 1.01.033\r\n')
        text.append('    Runtime : %s\r\n\r\n' %(self._fw_version['version_dot']))

        text.append('Checksum :\r\n')
        text.append('    PROM    : %s\r\n' %(self._checksum_prom))
        text.append('    Runtime : %s\r\n\r\n' %(self._checksum_runtime))


        text.append('Known issues:\r\n')
        text.append('-------------\r\n\r\n')
        return text

    def release_notes(self):
        last_release_note = self.get_last_releasenote_dir()
        if self.is_last_releasenote_local():
            with open(last_release_note, 'r') as f:
                content = f.readlines()
        else:
            ftp = RFtp(self._db.get_ftp_host())
            ftp.login(self._db.get_ftp_user(), self._db.get_ftp_passwd())
            dir = os.path.dirname(last_release_note)
            nm = last_release_note.split('/')[-1]
            buf = io.BytesIO()
            ftp.download_file_to_buf(buf, dir, nm)
            buf.seek(0)
            content = [i.decode() for i in buf.readlines()]
            ftp.close()
        head = content[0:10]
        history = content[10:]
        with open(self._ori_path['rn'], 'w') as fp:
            fp.writelines(head)
            fp.writelines(self.get_release_content())
            fp.writelines(history)

    def hex(self) -> str:
        return str(self._hex)

    def allhex(self) -> str:
        return str(self._allhex)

    def bin(self) -> str:
        return str(self._bin)

    def img(self) -> str:
        return str(self._img)

    def mib(self) -> str:
        return str(self._mib)

    def iss(self) -> str:
        return str(self._iss)

    def cmds(self) -> str:
        return str(self._cmds)

    def release_note(self) -> str:
        return str(self._release_note)

    def init_path(self):
        no_suffix = '-'.join([
            self._model_alias['hori_line_no_slash'],
            self._hw_version['version_tail'],
            self._fw_version['version_hori_line']
        ])
        self._hex = no_suffix + '.hex'
        self._allhex = no_suffix + '-ALL.hex'
        self._bin = no_suffix + '.bin'
        self._img = no_suffix + '.img'
        self._boot = no_suffix + '-BOOT.hex'
        self._fs = no_suffix + '-FS.hex'
        self._os = no_suffix + '-OS.hex'
        self._mib = '-'.join([
            self.model_alias()['hori_line_no_slash'],
            self._mib_hw_version,
            self._mib_sw_version['version_hori_line']
        ]) + '.mib'
        self._iss = 'ISS.exe'
        self._cmds = 'non_backdoor_cmds.csv'
        self._release_note = no_suffix + '_FW_Release Note.txt'

        join = 'image' + '/' + '-'.join([
            self._model_alias['hori_line_no_slash'],
            self._hw_version['version_tail']
        ]) + '/'
        d = self._dir.joinpath(join)
        self._ori_path = {
            'hex': d.joinpath(self._hex),
            'allhex': d.joinpath(self._allhex),
            'bin': d.joinpath(self._bin),
            'img': d.joinpath(self._img),
            'boot': d.joinpath(self._boot),
            'fs': d.joinpath(self._fs),
            'os': d.joinpath(self._os),
            'mib': d.joinpath(self._mib),
            'cmds' : d.joinpath(self._cmds),
            'rn' : d.joinpath(self._release_note),
            'iss' : d.joinpath(self._iss)
        }
        # TODO: .......
        # extract prom and runtime checksum from hex and boot hex
        hex = self._ori_path['hex']
        boot_hex = self._ori_path['boot']
        cameo_tag = constdb.cameo_tag_in_hex()
        # runtime checksum
        with open(str(hex), 'rb') as bf:
            str_out = binascii.b2a_hex(bf.read()).decode()
            goto = str_out.find(cameo_tag) + 32
            str_out_left = str_out[goto:]
            cameo_tag_start = str_out_left.find(cameo_tag) + len(cameo_tag) + 8
            cameo_tag_end = cameo_tag_start + 8
            runtime_checksum = str_out_left[cameo_tag_start:cameo_tag_end]
            self._checksum_runtime = '0x' + runtime_checksum
        # prom_checksum
        with open(str(boot_hex), 'rb') as bf:
            str_out = binascii.b2a_hex(bf.read()).decode()
            cameo_tag_start = \
                str_out.find(cameo_tag) + len(cameo_tag) + 8
            cameo_tag_end = cameo_tag_start + 8
            prom_checksum = str_out[cameo_tag_start:cameo_tag_end]
            self._checksum_prom = '0x' + prom_checksum

    def ori_path(self):
        return self._ori_path

    def model_alias(self):
        return self._model_alias


class RDB:
    def __init__(self):
        self._var_ftp_host = RelStrVar()
        self._var_ftp_user = RelStrVar()
        self._var_ftp_passwd = RelStrVar()
        self._var_release_by = RelStrVar()
        self._var_release_dir = RelStrVar()
        self._var_release_to_dir = RelStrVar()
        self._init_var_done = False

        self._var_new_feature = RelStrVar()
        self._var_bug_fixed = RelStrVar()

        self._models = []
        """ List (ModelDB) """

        self._dir_to_model = {}
        """ Dict (str->str): map model directory to model name
            Ex: DGS-1210-28ME-B1 --> WS5-DGS-1210-28-E1 """

        self._model_to_dir = {}
        """ Dict (str->str): map model name to model directory
            Ex: WS5-DGS-1210-28-E1 --> DGS-1210-28ME-B1 """

    def append_model(self, model: ModelDB):
        self._models.append(model)

    def models_name(self) -> []:
        """ Return model name list """
        return [m.__str__() for m in self._models]

    def models(self) -> []:
        """ Return ModelDB list """
        return self._models

    def init_entity(self, sync_all: bool = False):
        """ this init routine should be called after Tk() created,
            because tk.StringVar will save something to default root. """
        if self._init_var_done is True:
            return
        self._var_ftp_host.init_var(sync=sync_all)
        self._var_ftp_user.init_var(sync=sync_all)
        self._var_ftp_passwd.init_var(sync=sync_all)
        self._var_release_by.init_var(sync=sync_all)
        self._var_release_dir.init_var(sync=sync_all)
        self._var_release_to_dir.init_var(sync=sync_all)
        self._var_new_feature.init_var(sync=sync_all)
        self._var_bug_fixed.init_var(sync=sync_all)
        self._init_var_done = True

    def var_global_new_feature(self):
        return self._var_new_feature.var()

    def get_global_new_feature(self, cur_var: bool = False):
        if cur_var:
            return self._var_new_feature.get_var()
        else:
            return self._var_new_feature.get_raw()

    def set_global_new_feature(self, nf: str, sync_var: bool = False):
        """ If sync_var is true, this will also updates tkinter StringVar """
        if self._var_new_feature:
            if sync_var:
                self._var_new_feature.set_var(nf)
            return self._var_new_feature.set_raw(value=nf)

    def var_global_bug_fixed(self):
        return self._var_bug_fixed.var()

    def get_global_bug_fixed(self, cur_var: bool = False):
        if cur_var:
            return self._var_bug_fixed.get_var()
        else:
            return self._var_bug_fixed.get_raw()

    def set_global_bug_fixed(self, bf: str, sync_var: bool = False):
        """ If sync_var is true, this will also updates tkinter StringVar """
        if self._var_bug_fixed:
            if sync_var:
                self._var_bug_fixed.set_var(bf)
            return self._var_bug_fixed.set_raw(value=bf)

    def sync_global_new_feature(self):
        """ Sync global new feature to db model db """
        self._var_new_feature.sync()
        for m in self._models:
            m.set_new_feature(self._var_new_feature.get_raw())

    def sync_global_bug_fixed(self):
        """ Sync global bug fixed to db model db """
        self._var_bug_fixed.sync()
        for m in self._models:
            m.set_bug_fixed(self._var_bug_fixed.get_raw())

    def var_ftp_host(self):
        """ Return the tkinter StringVar object """
        return self._var_ftp_host.var()

    def set_ftp_host(self, host: str, sync_var: bool = False):
        """ If sync_var is true, this will also updates tkinter StringVar """
        if self._var_ftp_host:
            if sync_var:
                self._var_ftp_host.set_var(host)
            return self._var_ftp_host.set_raw(value=host)

    def get_ftp_host(self, cur_var: bool = False) -> str:
        """ If cur_var is true, get value from
        tkinter StringVar instead the raw value """
        if cur_var:
            return self._var_ftp_host.get_var()
        else:
            return self._var_ftp_host.get_raw()

    def var_ftp_user(self):
        return self._var_ftp_user.var()

    def set_ftp_user(self, user: str, sync_var: bool = False):
        if self._var_ftp_user:
            if sync_var:
                self._var_ftp_user.set_var(user)
            return self._var_ftp_user.set_raw(value=user)

    def get_ftp_user(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_ftp_user.get_var()
        else:
            return self._var_ftp_user.get_raw()

    def var_ftp_passwd(self):
        return self._var_ftp_passwd.var()

    def set_ftp_passwd(self, passwd: str, sync_var: bool = False):
        if self._var_ftp_passwd:
            if sync_var:
                self._var_ftp_passwd.set_var(passwd)
            return self._var_ftp_passwd.set_raw(value=passwd)

    def get_ftp_passwd(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_ftp_passwd.get_var()
        else:
            return self._var_ftp_passwd.get_raw()

    def var_release_by(self):
        return self._var_release_by.var()

    def set_release_by(self, release_by: str, sync_var: bool = False):
        if self._var_release_by:
            if sync_var:
                self._var_release_by.set_var(release_by)
            return self._var_release_by.set_raw(value=release_by)

    def get_release_by(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_release_by.get_var()
        else:
            return self._var_release_by.get_raw()

    def var_release_dir(self):
        return self._var_release_dir.var()

    def set_release_dir(self, release_dir: str, sync_var: bool = False):
        if self._var_release_dir:
            if sync_var:
                self._var_release_dir.set_var(release_dir)
            return self._var_release_dir.set_raw(value=release_dir)

    def get_release_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_release_dir.get_var()
        else:
            return self._var_release_dir.get_raw()

    def var_release_to_dir(self):
        return self._var_release_to_dir.var()

    def set_release_to_dir(self, release_to_dir: str, sync_var: bool = False):
        if self._var_release_to_dir:
            if sync_var:
                self._var_release_to_dir.set_var(release_to_dir)
            return self._var_release_to_dir.set_raw(value=release_to_dir)

    def get_release_to_dir(self, cur_var: bool = False) -> str:
        if cur_var:
            return self._var_release_to_dir.get_var()
        else:
            return self._var_release_to_dir.get_raw()

    def set_dir_model(self, dir: str, model: str):
        self._dir_to_model[dir] = model

    def set_model_dir(self, model: str, dir: str):
        self._model_to_dir[model] = dir
